/*
 * CITS2230 Operating Systems - Programming Project
 * Author:           Guilherme R. Lampert
 * Student number:   21203005
 */

#include "Common.hpp"
#include "Process.hpp"
#include "MemoryManager.hpp"

const int MemoryManager::CacheAccessTime = 1;
const int MemoryManager::MainMemoryAccessTime = 2;

MemoryManager::MemoryManager() : currentProcess(0)
{
	DLog("Creating memory manager...");
}

void MemoryManager::PrintMemoryDump(std::ostream & ostr, int timeStamp) const
{
	ostr << "Memory simulation at time " << timeStamp << std::endl;

	ostr << "Cache Memory:" << std::endl;
	for (int i = 0; i < ARRAY_SIZE(cacheMemory); ++i)
	{
		if (cacheMemory[i].owner)
		{
			ostr << "Page " << i+1 << ": " << cacheMemory[i].owner->Name().c_str() << std::endl;

			ostr << std::setfill('0') << std::setw(2) <<
				cacheMemory[i].lines[0].first+1 << ": " << cacheMemory[i].lines[0].second.c_str() << std::endl;

			ostr << std::setfill('0') << std::setw(2) <<
				cacheMemory[i].lines[1].first+1 << ": " << ((cacheMemory[i].lines[1].second != "") ?
				cacheMemory[i].lines[1].second.c_str() : "EMPTY") << std::endl;
		}
		else
		{
			ostr << "Page " << i+1 << ": EMPTY" << std::endl;
		}
	}

	ostr << "Main Memory:" << std::endl;
	for (int i = 0; i < ARRAY_SIZE(mainMemory); ++i)
	{
		if (mainMemory[i].owner)
		{
			ostr << "Frame " << i+1 << ": " << mainMemory[i].owner->Name().c_str() << std::endl;

			ostr << std::setfill('0') << std::setw(2) <<
				mainMemory[i].lines[0].first+1 << ": " << mainMemory[i].lines[0].second.c_str() << std::endl;

			ostr << std::setfill('0') << std::setw(2) <<
				mainMemory[i].lines[1].first+1 << ": " << ((mainMemory[i].lines[1].second != "") ?
				mainMemory[i].lines[1].second.c_str() : "EMPTY") << std::endl;
		}
		else
		{
			ostr << "Frame " << i+1 << ": EMPTY" << std::endl;
		}
	}

	ostr << std::endl;
}

void MemoryManager::LoadProcess(Process * p)
{
	DLog("Loading process " << p->Name().c_str());

	currentProcess = p;

	// Load 4 lines of "code" at process startup
	LoadToMainMemory(0); // Loads 0 and 1

	if (currentProcess->CodeLines().size() > 2)
	{
		LoadToMainMemory(2); // Loads 2 and 3
	}
}

void MemoryManager::UnloadProcess(Process * p)
{
	// Find all pages that belong to process 'p' and clear them
	for (int i = 0; i < ARRAY_SIZE(mainMemory); ++i)
	{
		if (mainMemory[i].owner == p)
		{
			mainMemory[i].owner = 0;
			mainMemory[i].lastReferenceTime = 0;
			mainMemory[i].lines[0].first  = -1;
			mainMemory[i].lines[0].second = "";
			mainMemory[i].lines[1].first  = -1;
			mainMemory[i].lines[1].second = "";
		}
	}
}

MemoryManager::MemoryAccessResult MemoryManager::NextCodeLine(int & simulationClock, std::string & codeLine)
{
	assert(currentProcess != 0);

	const int nextCodeLine = currentProcess->NextCodeLine();

	if (nextCodeLine < (int)currentProcess->CodeLines().size())
	{
		MemoryAccessResult result = ReadFromCache(nextCodeLine, codeLine);

		if (result == CacheMiss)
		{
			result = LoadToCache(nextCodeLine, simulationClock);

			// This read will succeed because we still have code
			// and it is now in the cache memory for sure.
			ReadFromCache(nextCodeLine, codeLine);

			simulationClock += MainMemoryAccessTime;
		}
		else
		{
			simulationClock += CacheAccessTime;
		}

		return (result);
	}
	else
	{
		return (NoMoreCode);
	}
}

MemoryManager::MemoryAccessResult MemoryManager::ReadFromCache(int line, std::string & code) const
{
	// Iterate thru the cache and try to find our code line
	for (int i = 0; i < ARRAY_SIZE(cacheMemory); ++i)
	{
		if (cacheMemory[i].FindCode(currentProcess, line, code))
		{
			return (Success);
		}
	}

	return (CacheMiss);
}

MemoryManager::MemoryAccessResult MemoryManager::LoadToCache(int line, int simulationClock)
{
	// First we check if the requested line is in the main memory,
	// if so, load those pages to the cache and return,
	// if not it is a page fault, we have to then load new pages into the main memory,
	// and only after that fill the cache.

	std::string code; // Unused

	for (int i = 0; i < ARRAY_SIZE(mainMemory); ++i)
	{
		if (mainMemory[i].FindCode(currentProcess, line, code))
		{
			// Found page
			for (int j = 0; j < ARRAY_SIZE(cacheMemory); ++j)
			{
				if ((i + j) < ARRAY_SIZE(mainMemory))
				{
					mainMemory[i + j].lastReferenceTime = simulationClock;
					cacheMemory[j] = mainMemory[i + j];
				}
				else
				{
					cacheMemory[j] = Page();
				}
			}
			return (CacheMiss); // Just a cache miss, no page fault
		}
	}

	// So if we get here, it is a page fault
	// gonna have to load it...
	LoadToMainMemory(line);

	// It is safe to recursively call this function,
	// because we know for sure that the requested page
	// exists somewhere, and so it will be loaded into main memory by now.
	LoadToCache(line, simulationClock);

	return (PageFault);
}

void MemoryManager::LoadToMainMemory(int line)
{
	// We need to make room for 1 page, so first check
	// if there is and empty page laying around.
	// if no empty pages are available, we unload the least
	// recently used and replace it with the requested one.

	for (int i = 0; i < ARRAY_SIZE(mainMemory); ++i)
	{
		if (mainMemory[i].owner == 0) // Is empty
		{
			mainMemory[i].owner = currentProcess;
			mainMemory[i].lines[0].first = line;
			mainMemory[i].lines[0].second = currentProcess->CodeLines()[line];

			++line; // Load the next line if there is one, or just leave the rest of the page empty
			if (line < (int)currentProcess->CodeLines().size())
			{
				mainMemory[i].lines[1].first = line;
				mainMemory[i].lines[1].second = currentProcess->CodeLines()[line];
			}
			else
			{
				mainMemory[i].lines[1].first = -1;
				mainMemory[i].lines[1].second = "";
			}
			return;
		}
	}

	// No empty pages, will have to "evict" one:

	int lowestReferenceTime = 0x7FFFFFFF;
	Page * candidate = 0;

	for (int i = 0; i < ARRAY_SIZE(mainMemory); ++i)
	{
		// Find the page with the lowest reference time
		if (mainMemory[i].lastReferenceTime <= lowestReferenceTime)
		{
			lowestReferenceTime = mainMemory[i].lastReferenceTime;
			candidate = &mainMemory[i];
		}
	}

	assert(candidate != 0);

	candidate->owner = currentProcess;
	candidate->lines[0].first = line;
	candidate->lines[0].second = currentProcess->CodeLines()[line];

	++line; // Load the next line if there is one, or just leave the rest of the page empty
	if (line < (int)currentProcess->CodeLines().size())
	{
		candidate->lines[1].first = line;
		candidate->lines[1].second = currentProcess->CodeLines()[line];
	}
	else
	{
		candidate->lines[1].first = -1;
		candidate->lines[1].second = "";
	}
}

MemoryManager::~MemoryManager()
{
	DLog("Destroying memory manager...");
}
